package de.upb.aquarium.water.pump;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import de.upb.aquarium.World;
import de.upb.aquarium.api.utility.VectorDouble;
import de.upb.aquarium.api.utility.VectorFloat;
import de.upb.aquarium.water.bubbles.BubbleManager;
import de.upb.aquarium.water.bubbles.PumpBubbleSource;

public class WaterPump {

	private static int numberOfParticles = 0;
	private static final int bumperPart = 10;
	private static int numBubbleSources = 25; 

	private VectorDouble[] controlPoints;

	private VectorDouble downDirection;
	private double downSquare;
	private double angle;
	private boolean initialize;
	private boolean on;
	
	private List<PumpBubbleSource> bubbleSources;
	
	public WaterPump(
			VectorDouble origin,
			VectorDouble target,
			VectorDouble downDirection,
			double radius,
			double angle,
			int numCurves) {
		
		controlPoints = new VectorDouble[numCurves];
		
		this.downDirection = downDirection;
		this.downSquare = downDirection.norm()*downDirection.norm();
		this.angle = angle;
		this.initialize = true;
		this.on = false;
		
		bubbleSources = new ArrayList<PumpBubbleSource>();
		
		for (int i = 0; i < numCurves; i++)
		{
			VectorDouble o = generateOrigin(origin, radius);
			VectorDouble t = generateTarget(target, radius);
			VectorDouble m = calculateMidPoint(o, t);
			
			controlPoints[i] = new VectorDouble(new double[]
			                {
								o.data[0],
								o.data[1],
								o.data[2],

								m.data[0],
								m.data[1],
								m.data[2],
								
								t.data[0],
								t.data[1],
								t.data[2],
			                });
			
			if (i % (numCurves/numBubbleSources) == 0)
			{
				VectorFloat pos = new VectorFloat(new VectorDouble(controlPoints[i].data, 6, 3).data);
				pos.data[1] = 4.74f;
				bubbleSources.add(new PumpBubbleSource(
						pos,
						new VectorFloat(downDirection.mul(-1.0f).data),
						new VectorFloat(downDirection.mul(0.75).data),
						2.0, 1.0, 0.1));
			}
		}
		
		
	}
	
	public List<VectorDouble> getPotentialWavePositions()
	{
		List<VectorDouble> result = new LinkedList<VectorDouble>();
		
		for (int i = 0; i < controlPoints.length; i++)
		{
			result.add(new VectorDouble(controlPoints[i].data, 6, 3));
		}
		
		return result;
	}

	public List<WaterParticle> generate(int number)
	{
		List<WaterParticle> result = new LinkedList<WaterParticle>();
		
		if (on)
		{
			for (int i = 0; i < number; i++)
			{
				numberOfParticles++;
			
				int index = (int)(Math.random()*controlPoints.length);
				
				WaterParticle particle;
				if (initialize)
				{
					particle = new WaterParticle(controlPoints[index].data,
							World.getInstance().getGlobalTime() - Math.random()*WaterParticle.lifeTime);
				}
				else
				{
					particle = new WaterParticle(controlPoints[index].data,
							World.getInstance().getGlobalTime());
				}
				
				particle.setBumper(numberOfParticles == bumperPart);
				if (numberOfParticles == bumperPart)
				{
					numberOfParticles = 0;
				}
	
				result.add(particle);
			}
			
			if (initialize)
			{
				initialize = false;
			}
		}
		
		return result;
	}
	
	public boolean isOn() {
		return on;
	}

	public void setOn(boolean on) {
		this.on = on;
		if (on)
		{
			initialize = true;
			for (PumpBubbleSource source : bubbleSources)
			{
				source.reset();
				BubbleManager.getInstance().addBubbleSource(source);
			}
		}
		else
		{
			for (PumpBubbleSource source : bubbleSources)
			{
				BubbleManager.getInstance().removeBubbleSource(source);
			}
		}
	}

	private VectorDouble generateTarget(VectorDouble target, double radius)
	{
		VectorDouble offset = new VectorDouble(new double[] {
														2*Math.random()-1.0,
														2*Math.random()-1.0,
														2*Math.random()-1.0,
		                                        	});
		offset.normalize();
		
		VectorDouble result = target.add(offset.mul(radius));
		result = result.add(downDirection.mul(target.sub(result).dot(downDirection)/downSquare));
		
		return result;
	}
	
	private VectorDouble generateOrigin(VectorDouble origin, double radius)
	{
		VectorDouble offset = new VectorDouble(new double[] {
				2*Math.random()-1.0,
				2*Math.random()-1.0,
				2*Math.random()-1.0,
        	});
		offset.normalize();
		
		return origin.add(offset.mul(radius));
	}
	
	private VectorDouble calculateMidPoint(VectorDouble origin, VectorDouble target)
	{
		return downDirection.mul(origin.sub(target).dot(downDirection)).add(target);
	}
}
